/**
 * Dom7 2.0.7
 * Minimalistic JavaScript library for DOM manipulation, with a jQuery-compatible API
 * http://framework7.io/docs/dom.html
 *
 * Copyright 2018, Vladimir Kharlampidi
 * The iDangero.us
 * http://www.idangero.us/
 *
 * Licensed under MIT
 *
 * Released on: June 14, 2018
 */
import {
  document,
  window
} from 'ssr-window'

class Dom7 {
  constructor (arr) {
    const self = this
    // Create array-like object
    for (let i = 0; i < arr.length; i += 1) {
      self[i] = arr[i]
    }
    self.length = arr.length
    // Return collection with methods
    return this
  }
}

function $ (selector, context) {
  const arr = []
  let i = 0
  if (selector && !context) {
    if (selector instanceof Dom7) {
      return selector
    }
  }
  if (selector) {
    // String
    if (typeof selector === 'string') {
      let els
      let tempParent
      const html = selector.trim()
      if (html.indexOf('<') >= 0 && html.indexOf('>') >= 0) {
        let toCreate = 'div'
        if (html.indexOf('<li') === 0) toCreate = 'ul'
        if (html.indexOf('<tr') === 0) toCreate = 'tbody'
        if (html.indexOf('<td') === 0 || html.indexOf('<th') === 0) toCreate = 'tr'
        if (html.indexOf('<tbody') === 0) toCreate = 'table'
        if (html.indexOf('<option') === 0) toCreate = 'select'
        tempParent = document.createElement(toCreate)
        tempParent.innerHTML = html
        for (i = 0; i < tempParent.childNodes.length; i += 1) {
          arr.push(tempParent.childNodes[i])
        }
      } else {
        if (!context && selector[0] === '#' && !selector.match(/[ .<>:~]/)) {
          // Pure ID selector
          els = [document.getElementById(selector.trim().split('#')[1])]
        } else {
          // Other selectors
          els = (context || document).querySelectorAll(selector.trim())
        }
        for (i = 0; i < els.length; i += 1) {
          if (els[i]) arr.push(els[i])
        }
      }
    } else if (selector.nodeType || selector === window || selector === document) {
      // Node/element
      arr.push(selector)
    } else if (selector.length > 0 && selector[0].nodeType) {
      // Array of elements or instance of Dom
      for (i = 0; i < selector.length; i += 1) {
        arr.push(selector[i])
      }
    }
  }
  return new Dom7(arr)
}

$.fn = Dom7.prototype
$.Class = Dom7
$.Dom7 = Dom7

function unique (arr) {
  const uniqueArray = []
  for (let i = 0; i < arr.length; i += 1) {
    if (uniqueArray.indexOf(arr[i]) === -1) uniqueArray.push(arr[i])
  }
  return uniqueArray
}

function toCamelCase (string) {
  return string.toLowerCase().replace(/-(.)/g, (match, group1) => group1.toUpperCase())
}

function requestAnimationFrame (callback) {
  if (window.requestAnimationFrame) return window.requestAnimationFrame(callback)
  else if (window.webkitRequestAnimationFrame) return window.webkitRequestAnimationFrame(callback)
  return window.setTimeout(callback, 1000 / 60)
}

function cancelAnimationFrame (id) {
  if (window.cancelAnimationFrame) return window.cancelAnimationFrame(id)
  else if (window.webkitCancelAnimationFrame) return window.webkitCancelAnimationFrame(id)
  return window.clearTimeout(id)
}

// Classes and attributes
function addClass (className) {
  if (typeof className === 'undefined') {
    return this
  }
  const classes = className.split(' ')
  for (let i = 0; i < classes.length; i += 1) {
    for (let j = 0; j < this.length; j += 1) {
      if (typeof this[j] !== 'undefined' && typeof this[j].classList !== 'undefined') this[j].classList.add(classes[i])
    }
  }
  return this
}

function removeClass (className) {
  const classes = className.split(' ')
  for (let i = 0; i < classes.length; i += 1) {
    for (let j = 0; j < this.length; j += 1) {
      if (typeof this[j] !== 'undefined' && typeof this[j].classList !== 'undefined') this[j].classList.remove(classes[i])
    }
  }
  return this
}

function hasClass (className) {
  if (!this[0]) return false
  return this[0].classList.contains(className)
}

function toggleClass (className) {
  const classes = className.split(' ')
  for (let i = 0; i < classes.length; i += 1) {
    for (let j = 0; j < this.length; j += 1) {
      if (typeof this[j] !== 'undefined' && typeof this[j].classList !== 'undefined') this[j].classList.toggle(classes[i])
    }
  }
  return this
}

function attr (attrs, value) {
  if (arguments.length === 1 && typeof attrs === 'string') {
    // Get attr
    if (this[0]) return this[0].getAttribute(attrs)
    return undefined
  }

  // Set attrs
  for (let i = 0; i < this.length; i += 1) {
    if (arguments.length === 2) {
      // String
      this[i].setAttribute(attrs, value)
    } else {
      // Object
      // eslint-disable-next-line
      for (const attrName in attrs) {
        this[i][attrName] = attrs[attrName]
        this[i].setAttribute(attrName, attrs[attrName])
      }
    }
  }
  return this
}
// eslint-disable-next-line
function removeAttr(attr) {
  for (let i = 0; i < this.length; i += 1) {
    this[i].removeAttribute(attr)
  }
  return this
}
// eslint-disable-next-line
function prop(props, value) {
  if (arguments.length === 1 && typeof props === 'string') {
    // Get prop
    if (this[0]) return this[0][props]
  } else {
    // Set props
    for (let i = 0; i < this.length; i += 1) {
      if (arguments.length === 2) {
        // String
        this[i][props] = value
      } else {
        // Object
        // eslint-disable-next-line
        for (const propName in props) {
          this[i][propName] = props[propName]
        }
      }
    }
    return this
  }
}

function data (key, value) {
  let el
  if (typeof value === 'undefined') {
    el = this[0]
    // Get value
    if (el) {
      if (el.dom7ElementDataStorage && (key in el.dom7ElementDataStorage)) {
        return el.dom7ElementDataStorage[key]
      }

      const dataKey = el.getAttribute(`data-${key}`)
      if (dataKey) {
        return dataKey
      }
      return undefined
    }
    return undefined
  }

  // Set value
  for (let i = 0; i < this.length; i += 1) {
    el = this[i]
    if (!el.dom7ElementDataStorage) el.dom7ElementDataStorage = {}
    el.dom7ElementDataStorage[key] = value
  }
  return this
}

function removeData (key) {
  for (let i = 0; i < this.length; i += 1) {
    const el = this[i]
    if (el.dom7ElementDataStorage && el.dom7ElementDataStorage[key]) {
      el.dom7ElementDataStorage[key] = null
      delete el.dom7ElementDataStorage[key]
    }
  }
}

function dataset () {
  const el = this[0]
  if (!el) return undefined
  const dataset = {}; // eslint-disable-line
  if (el.dataset) {
    // eslint-disable-next-line
    for (const dataKey in el.dataset) {
      dataset[dataKey] = el.dataset[dataKey]
    }
  } else {
    for (let i = 0; i < el.attributes.length; i += 1) {
      // eslint-disable-next-line
      const attr = el.attributes[i];
      if (attr.name.indexOf('data-') >= 0) {
        dataset[toCamelCase(attr.name.split('data-')[1])] = attr.value
      }
    }
  }
  // eslint-disable-next-line
  for (const key in dataset) {
    if (dataset[key] === 'false') dataset[key] = false
    else if (dataset[key] === 'true') dataset[key] = true
    else if (parseFloat(dataset[key]) === dataset[key] * 1) dataset[key] *= 1
  }
  return dataset
}

function val (value) {
  const dom = this
  if (typeof value === 'undefined') {
    if (dom[0]) {
      if (dom[0].multiple && dom[0].nodeName.toLowerCase() === 'select') {
        const values = []
        for (let i = 0; i < dom[0].selectedOptions.length; i += 1) {
          values.push(dom[0].selectedOptions[i].value)
        }
        return values
      }
      return dom[0].value
    }
    return undefined
  }

  for (let i = 0; i < dom.length; i += 1) {
    const el = dom[i]
    if (Array.isArray(value) && el.multiple && el.nodeName.toLowerCase() === 'select') {
      for (let j = 0; j < el.options.length; j += 1) {
        el.options[j].selected = value.indexOf(el.options[j].value) >= 0
      }
    } else {
      el.value = value
    }
  }
  return dom
}
// Transforms
// eslint-disable-next-line
function transform(transform) {
  for (let i = 0; i < this.length; i += 1) {
    const elStyle = this[i].style
    elStyle.webkitTransform = transform
    elStyle.transform = transform
  }
  return this
}

function transition (duration) {
  if (typeof duration !== 'string') {
    duration = `${duration}ms`; // eslint-disable-line
  }
  for (let i = 0; i < this.length; i += 1) {
    const elStyle = this[i].style
    elStyle.webkitTransitionDuration = duration
    elStyle.transitionDuration = duration
  }
  return this
}
// Events
function on (...args) {
  let [eventType, targetSelector, listener, capture] = args
  if (typeof args[1] === 'function') {
    [eventType, listener, capture] = args
    targetSelector = undefined
  }
  if (!capture) capture = false

  function handleLiveEvent (e) {
    const target = e.target
    if (!target) return
    const eventData = e.target.dom7EventData || []
    if (eventData.indexOf(e) < 0) {
      eventData.unshift(e)
    }
    if ($(target).is(targetSelector)) listener.apply(target, eventData)
    else {
      const parents = $(target).parents(); // eslint-disable-line
      for (let k = 0; k < parents.length; k += 1) {
        if ($(parents[k]).is(targetSelector)) listener.apply(parents[k], eventData)
      }
    }
  }

  function handleEvent (e) {
    const eventData = e && e.target ? e.target.dom7EventData || [] : []
    if (eventData.indexOf(e) < 0) {
      eventData.unshift(e)
    }
    listener.apply(this, eventData)
  }
  const events = eventType.split(' ')
  let j
  for (let i = 0; i < this.length; i += 1) {
    const el = this[i]
    if (!targetSelector) {
      for (j = 0; j < events.length; j += 1) {
        const event = events[j]
        if (!el.dom7Listeners) el.dom7Listeners = {}
        if (!el.dom7Listeners[event]) el.dom7Listeners[event] = []
        el.dom7Listeners[event].push({
          listener,
          proxyListener: handleEvent
        })
        el.addEventListener(event, handleEvent, capture)
      }
    } else {
      // Live events
      for (j = 0; j < events.length; j += 1) {
        const event = events[j]
        if (!el.dom7LiveListeners) el.dom7LiveListeners = {}
        if (!el.dom7LiveListeners[event]) el.dom7LiveListeners[event] = []
        el.dom7LiveListeners[event].push({
          listener,
          proxyListener: handleLiveEvent
        })
        el.addEventListener(event, handleLiveEvent, capture)
      }
    }
  }
  return this
}

function off (...args) {
  let [eventType, targetSelector, listener, capture] = args
  if (typeof args[1] === 'function') {
    [eventType, listener, capture] = args
    targetSelector = undefined
  }
  if (!capture) capture = false

  const events = eventType.split(' ')
  for (let i = 0; i < events.length; i += 1) {
    const event = events[i]
    for (let j = 0; j < this.length; j += 1) {
      const el = this[j]
      let handlers
      if (!targetSelector && el.dom7Listeners) {
        handlers = el.dom7Listeners[event]
      } else if (targetSelector && el.dom7LiveListeners) {
        handlers = el.dom7LiveListeners[event]
      }
      if (handlers && handlers.length) {
        for (let k = handlers.length - 1; k >= 0; k -= 1) {
          const handler = handlers[k]
          if (listener && handler.listener === listener) {
            el.removeEventListener(event, handler.proxyListener, capture)
            handlers.splice(k, 1)
          } else if (!listener) {
            el.removeEventListener(event, handler.proxyListener, capture)
            handlers.splice(k, 1)
          }
        }
      }
    }
  }
  return this
}

function once (...args) {
  const dom = this
  let [eventName, targetSelector, listener, capture] = args
  if (typeof args[1] === 'function') {
    [eventName, listener, capture] = args
    targetSelector = undefined
  }

  function proxy (...eventArgs) {
    listener.apply(this, eventArgs)
    dom.off(eventName, targetSelector, proxy, capture)
  }
  return dom.on(eventName, targetSelector, proxy, capture)
}

function trigger (...args) {
  const events = args[0].split(' ')
  const eventData = args[1]
  for (let i = 0; i < events.length; i += 1) {
    const event = events[i]
    for (let j = 0; j < this.length; j += 1) {
      const el = this[j]
      let evt
      try {
        evt = new window.CustomEvent(event, {
          detail: eventData,
          bubbles: true,
          cancelable: true
        })
      } catch (e) {
        evt = document.createEvent('Event')
        evt.initEvent(event, true, true)
        evt.detail = eventData
      }
      // eslint-disable-next-line
      el.dom7EventData = args.filter((data, dataIndex) => dataIndex > 0);
      el.dispatchEvent(evt)
      el.dom7EventData = []
      delete el.dom7EventData
    }
  }
  return this
}

function transitionEnd (callback) {
  const events = ['webkitTransitionEnd', 'transitionend']
  const dom = this
  let i

  function fireCallBack (e) {
    /* jshint validthis:true */
    if (e.target !== this) return
    callback.call(this, e)
    for (i = 0; i < events.length; i += 1) {
      dom.off(events[i], fireCallBack)
    }
  }
  if (callback) {
    for (i = 0; i < events.length; i += 1) {
      dom.on(events[i], fireCallBack)
    }
  }
  return this
}

function animationEnd (callback) {
  const events = ['webkitAnimationEnd', 'animationend']
  const dom = this
  let i

  function fireCallBack (e) {
    if (e.target !== this) return
    callback.call(this, e)
    for (i = 0; i < events.length; i += 1) {
      dom.off(events[i], fireCallBack)
    }
  }
  if (callback) {
    for (i = 0; i < events.length; i += 1) {
      dom.on(events[i], fireCallBack)
    }
  }
  return this
}
// Sizing/Styles
function width () {
  if (this[0] === window) {
    return window.innerWidth
  }

  if (this.length > 0) {
    return parseFloat(this.css('width'))
  }

  return null
}

function outerWidth (includeMargins) {
  if (this.length > 0) {
    if (includeMargins) {
      // eslint-disable-next-line
      const styles = this.styles();
      return this[0].offsetWidth + parseFloat(styles.getPropertyValue('margin-right')) + parseFloat(styles.getPropertyValue('margin-left'))
    }
    return this[0].offsetWidth
  }
  return null
}

function height () {
  if (this[0] === window) {
    return window.innerHeight
  }

  if (this.length > 0) {
    return parseFloat(this.css('height'))
  }

  return null
}

function outerHeight (includeMargins) {
  if (this.length > 0) {
    if (includeMargins) {
      // eslint-disable-next-line
      const styles = this.styles();
      return this[0].offsetHeight + parseFloat(styles.getPropertyValue('margin-top')) + parseFloat(styles.getPropertyValue('margin-bottom'))
    }
    return this[0].offsetHeight
  }
  return null
}

function offset () {
  if (this.length > 0) {
    const el = this[0]
    const box = el.getBoundingClientRect()
    const body = document.body
    const clientTop = el.clientTop || body.clientTop || 0
    const clientLeft = el.clientLeft || body.clientLeft || 0
    const scrollTop = el === window ? window.scrollY : el.scrollTop
    const scrollLeft = el === window ? window.scrollX : el.scrollLeft
    return {
      top: (box.top + scrollTop) - clientTop,
      left: (box.left + scrollLeft) - clientLeft
    }
  }

  return null
}

function hide () {
  for (let i = 0; i < this.length; i += 1) {
    this[i].style.display = 'none'
  }
  return this
}

function show () {
  for (let i = 0; i < this.length; i += 1) {
    const el = this[i]
    if (el.style.display === 'none') {
      el.style.display = ''
    }
    if (window.getComputedStyle(el, null).getPropertyValue('display') === 'none') {
      // Still not visible
      el.style.display = 'block'
    }
  }
  return this
}

function styles () {
  if (this[0]) return window.getComputedStyle(this[0], null)
  return {}
}

function css (props, value) {
  let i
  if (arguments.length === 1) {
    if (typeof props === 'string') {
      if (this[0]) return window.getComputedStyle(this[0], null).getPropertyValue(props)
    } else {
      for (i = 0; i < this.length; i += 1) {
        // eslint-disable-next-line
        for (let prop in props) {
          this[i].style[prop] = props[prop]
        }
      }
      return this
    }
  }
  if (arguments.length === 2 && typeof props === 'string') {
    for (i = 0; i < this.length; i += 1) {
      this[i].style[props] = value
    }
    return this
  }
  return this
}

// Dom manipulation
function toArray () {
  const arr = []
  for (let i = 0; i < this.length; i += 1) {
    arr.push(this[i])
  }
  return arr
}
// Iterate over the collection passing elements to `callback`
function each (callback) {
  // Don't bother continuing without a callback
  if (!callback) return this
  // Iterate over the current collection
  for (let i = 0; i < this.length; i += 1) {
    // If the callback returns false
    if (callback.call(this[i], i, this[i]) === false) {
      // End the loop early
      return this
    }
  }
  // Return `this` to allow chained DOM operations
  return this
}

function forEach (callback) {
  // Don't bother continuing without a callback
  if (!callback) return this
  // Iterate over the current collection
  for (let i = 0; i < this.length; i += 1) {
    // If the callback returns false
    if (callback.call(this[i], this[i], i) === false) {
      // End the loop early
      return this
    }
  }
  // Return `this` to allow chained DOM operations
  return this
}

function filter (callback) {
  const matchedItems = []
  const dom = this
  for (let i = 0; i < dom.length; i += 1) {
    if (callback.call(dom[i], i, dom[i])) matchedItems.push(dom[i])
  }
  return new Dom7(matchedItems)
}

function map (callback) {
  const modifiedItems = []
  const dom = this
  for (let i = 0; i < dom.length; i += 1) {
    modifiedItems.push(callback.call(dom[i], i, dom[i]))
  }
  return new Dom7(modifiedItems)
}
// eslint-disable-next-line
function html(html) {
  if (typeof html === 'undefined') {
    return this[0] ? this[0].innerHTML : undefined
  }

  for (let i = 0; i < this.length; i += 1) {
    this[i].innerHTML = html
  }
  return this
}
// eslint-disable-next-line
function text(text) {
  if (typeof text === 'undefined') {
    if (this[0]) {
      return this[0].textContent.trim()
    }
    return null
  }

  for (let i = 0; i < this.length; i += 1) {
    this[i].textContent = text
  }
  return this
}

function is (selector) {
  const el = this[0]
  let compareWith
  let i
  if (!el || typeof selector === 'undefined') return false
  if (typeof selector === 'string') {
    if (el.matches) return el.matches(selector)
    else if (el.webkitMatchesSelector) return el.webkitMatchesSelector(selector)
    else if (el.msMatchesSelector) return el.msMatchesSelector(selector)

    compareWith = $(selector)
    for (i = 0; i < compareWith.length; i += 1) {
      if (compareWith[i] === el) return true
    }
    return false
  } else if (selector === document) return el === document
  else if (selector === window) return el === window

  if (selector.nodeType || selector instanceof Dom7) {
    compareWith = selector.nodeType ? [selector] : selector
    for (i = 0; i < compareWith.length; i += 1) {
      if (compareWith[i] === el) return true
    }
    return false
  }
  return false
}

function indexOf (el) {
  for (let i = 0; i < this.length; i += 1) {
    if (this[i] === el) return i
  }
  return -1
}

function index () {
  let child = this[0]
  let i
  if (child) {
    i = 0
    // eslint-disable-next-line
    while ((child = child.previousSibling) !== null) {
      if (child.nodeType === 1) i += 1
    }
    return i
  }
  return undefined
}
// eslint-disable-next-line
function eq(index) {
  if (typeof index === 'undefined') return this
  const length = this.length
  let returnIndex
  if (index > length - 1) {
    return new Dom7([])
  }
  if (index < 0) {
    returnIndex = length + index
    if (returnIndex < 0) return new Dom7([])
    return new Dom7([this[returnIndex]])
  }
  return new Dom7([this[index]])
}

function append (...args) {
  let newChild

  for (let k = 0; k < args.length; k += 1) {
    newChild = args[k]
    for (let i = 0; i < this.length; i += 1) {
      if (typeof newChild === 'string') {
        const tempDiv = document.createElement('div')
        tempDiv.innerHTML = newChild
        while (tempDiv.firstChild) {
          this[i].appendChild(tempDiv.firstChild)
        }
      } else if (newChild instanceof Dom7) {
        for (let j = 0; j < newChild.length; j += 1) {
          this[i].appendChild(newChild[j])
        }
      } else {
        this[i].appendChild(newChild)
      }
    }
  }

  return this
}
// eslint-disable-next-line
function appendTo(parent) {
  $(parent).append(this)
  return this
}

function prepend (newChild) {
  let i
  let j
  for (i = 0; i < this.length; i += 1) {
    if (typeof newChild === 'string') {
      const tempDiv = document.createElement('div')
      tempDiv.innerHTML = newChild
      for (j = tempDiv.childNodes.length - 1; j >= 0; j -= 1) {
        this[i].insertBefore(tempDiv.childNodes[j], this[i].childNodes[0])
      }
    } else if (newChild instanceof Dom7) {
      for (j = 0; j < newChild.length; j += 1) {
        this[i].insertBefore(newChild[j], this[i].childNodes[0])
      }
    } else {
      this[i].insertBefore(newChild, this[i].childNodes[0])
    }
  }
  return this
}
// eslint-disable-next-line
function prependTo(parent) {
  $(parent).prepend(this)
  return this
}

function insertBefore (selector) {
  const before = $(selector)
  for (let i = 0; i < this.length; i += 1) {
    if (before.length === 1) {
      before[0].parentNode.insertBefore(this[i], before[0])
    } else if (before.length > 1) {
      for (let j = 0; j < before.length; j += 1) {
        before[j].parentNode.insertBefore(this[i].cloneNode(true), before[j])
      }
    }
  }
}

function insertAfter (selector) {
  const after = $(selector)
  for (let i = 0; i < this.length; i += 1) {
    if (after.length === 1) {
      after[0].parentNode.insertBefore(this[i], after[0].nextSibling)
    } else if (after.length > 1) {
      for (let j = 0; j < after.length; j += 1) {
        after[j].parentNode.insertBefore(this[i].cloneNode(true), after[j].nextSibling)
      }
    }
  }
}

function next (selector) {
  if (this.length > 0) {
    if (selector) {
      if (this[0].nextElementSibling && $(this[0].nextElementSibling).is(selector)) {
        return new Dom7([this[0].nextElementSibling])
      }
      return new Dom7([])
    }

    if (this[0].nextElementSibling) return new Dom7([this[0].nextElementSibling])
    return new Dom7([])
  }
  return new Dom7([])
}

function nextAll (selector) {
  const nextEls = []
  let el = this[0]
  if (!el) return new Dom7([])
  while (el.nextElementSibling) {
    const next = el.nextElementSibling; // eslint-disable-line
    if (selector) {
      if ($(next).is(selector)) nextEls.push(next)
    } else nextEls.push(next)
    el = next
  }
  return new Dom7(nextEls)
}

function prev (selector) {
  if (this.length > 0) {
    const el = this[0]
    if (selector) {
      if (el.previousElementSibling && $(el.previousElementSibling).is(selector)) {
        return new Dom7([el.previousElementSibling])
      }
      return new Dom7([])
    }

    if (el.previousElementSibling) return new Dom7([el.previousElementSibling])
    return new Dom7([])
  }
  return new Dom7([])
}

function prevAll (selector) {
  const prevEls = []
  let el = this[0]
  if (!el) return new Dom7([])
  while (el.previousElementSibling) {
    const prev = el.previousElementSibling; // eslint-disable-line
    if (selector) {
      if ($(prev).is(selector)) prevEls.push(prev)
    } else prevEls.push(prev)
    el = prev
  }
  return new Dom7(prevEls)
}

function siblings (selector) {
  return this.nextAll(selector).add(this.prevAll(selector))
}

function parent (selector) {
  const parents = []; // eslint-disable-line
  for (let i = 0; i < this.length; i += 1) {
    if (this[i].parentNode !== null) {
      if (selector) {
        if ($(this[i].parentNode).is(selector)) parents.push(this[i].parentNode)
      } else {
        parents.push(this[i].parentNode)
      }
    }
  }
  return $(unique(parents))
}

function parents (selector) {
  const parents = []; // eslint-disable-line
  for (let i = 0; i < this.length; i += 1) {
    let parent = this[i].parentNode; // eslint-disable-line
    while (parent) {
      if (selector) {
        if ($(parent).is(selector)) parents.push(parent)
      } else {
        parents.push(parent)
      }
      parent = parent.parentNode
    }
  }
  return $(unique(parents))
}

function closest (selector) {
  let closest = this; // eslint-disable-line
  if (typeof selector === 'undefined') {
    return new Dom7([])
  }
  if (!closest.is(selector)) {
    closest = closest.parents(selector).eq(0)
  }
  return closest
}

function find (selector) {
  const foundElements = []
  for (let i = 0; i < this.length; i += 1) {
    const found = this[i].querySelectorAll(selector)
    for (let j = 0; j < found.length; j += 1) {
      foundElements.push(found[j])
    }
  }
  return new Dom7(foundElements)
}

function children (selector) {
  const children = []; // eslint-disable-line
  for (let i = 0; i < this.length; i += 1) {
    const childNodes = this[i].childNodes

    for (let j = 0; j < childNodes.length; j += 1) {
      if (!selector) {
        if (childNodes[j].nodeType === 1) children.push(childNodes[j])
      } else if (childNodes[j].nodeType === 1 && $(childNodes[j]).is(selector)) {
        children.push(childNodes[j])
      }
    }
  }
  return new Dom7(unique(children))
}

function remove () {
  for (let i = 0; i < this.length; i += 1) {
    if (this[i].parentNode) this[i].parentNode.removeChild(this[i])
  }
  return this
}

function detach () {
  return this.remove()
}

function add (...args) {
  const dom = this
  let i
  let j
  for (i = 0; i < args.length; i += 1) {
    const toAdd = $(args[i])
    for (j = 0; j < toAdd.length; j += 1) {
      dom[dom.length] = toAdd[j]
      dom.length += 1
    }
  }
  return dom
}

function empty () {
  for (let i = 0; i < this.length; i += 1) {
    const el = this[i]
    if (el.nodeType === 1) {
      for (let j = 0; j < el.childNodes.length; j += 1) {
        if (el.childNodes[j].parentNode) {
          el.childNodes[j].parentNode.removeChild(el.childNodes[j])
        }
      }
      el.textContent = ''
    }
  }
  return this
}

function scrollTo (...args) {
  let [left, top, duration, easing, callback] = args
  if (args.length === 4 && typeof easing === 'function') {
    callback = easing;
    [left, top, duration, callback, easing] = args
  }
  if (typeof easing === 'undefined') easing = 'swing'

  return this.each(function animate () {
    const el = this
    let currentTop
    let currentLeft
    let maxTop
    let maxLeft
    let newTop
    let newLeft
    let scrollTop; // eslint-disable-line
    let scrollLeft; // eslint-disable-line
    let animateTop = top > 0 || top === 0
    let animateLeft = left > 0 || left === 0
    if (typeof easing === 'undefined') {
      easing = 'swing'
    }
    if (animateTop) {
      currentTop = el.scrollTop
      if (!duration) {
        el.scrollTop = top
      }
    }
    if (animateLeft) {
      currentLeft = el.scrollLeft
      if (!duration) {
        el.scrollLeft = left
      }
    }
    if (!duration) return
    if (animateTop) {
      maxTop = el.scrollHeight - el.offsetHeight
      newTop = Math.max(Math.min(top, maxTop), 0)
    }
    if (animateLeft) {
      maxLeft = el.scrollWidth - el.offsetWidth
      newLeft = Math.max(Math.min(left, maxLeft), 0)
    }
    let startTime = null
    if (animateTop && newTop === currentTop) animateTop = false
    if (animateLeft && newLeft === currentLeft) animateLeft = false

    function render (time = new Date().getTime()) {
      if (startTime === null) {
        startTime = time
      }
      const progress = Math.max(Math.min((time - startTime) / duration, 1), 0)
      const easeProgress = easing === 'linear' ? progress : (0.5 - (Math.cos(progress * Math.PI) / 2))
      let done
      if (animateTop) scrollTop = currentTop + (easeProgress * (newTop - currentTop))
      if (animateLeft) scrollLeft = currentLeft + (easeProgress * (newLeft - currentLeft))
      if (animateTop && newTop > currentTop && scrollTop >= newTop) {
        el.scrollTop = newTop
        done = true
      }
      if (animateTop && newTop < currentTop && scrollTop <= newTop) {
        el.scrollTop = newTop
        done = true
      }
      if (animateLeft && newLeft > currentLeft && scrollLeft >= newLeft) {
        el.scrollLeft = newLeft
        done = true
      }
      if (animateLeft && newLeft < currentLeft && scrollLeft <= newLeft) {
        el.scrollLeft = newLeft
        done = true
      }

      if (done) {
        if (callback) callback()
        return
      }
      if (animateTop) el.scrollTop = scrollTop
      if (animateLeft) el.scrollLeft = scrollLeft
      requestAnimationFrame(render)
    }
    requestAnimationFrame(render)
  })
}
// scrollTop(top, duration, easing, callback) {
function scrollTop (...args) {
  let [top, duration, easing, callback] = args
  if (args.length === 3 && typeof easing === 'function') {
    [top, duration, callback, easing] = args
  }
  const dom = this
  if (typeof top === 'undefined') {
    if (dom.length > 0) return dom[0].scrollTop
    return null
  }
  return dom.scrollTo(undefined, top, duration, easing, callback)
}

function scrollLeft (...args) {
  let [left, duration, easing, callback] = args
  if (args.length === 3 && typeof easing === 'function') {
    [left, duration, callback, easing] = args
  }
  const dom = this
  if (typeof left === 'undefined') {
    if (dom.length > 0) return dom[0].scrollLeft
    return null
  }
  return dom.scrollTo(left, undefined, duration, easing, callback)
}

function animate (initialProps, initialParams) {
  const els = this
  const a = {
    props: Object.assign({}, initialProps),
    params: Object.assign({
      duration: 300,
      easing: 'swing' // or 'linear'
      /* Callbacks
      begin(elements)
      complete(elements)
      progress(elements, complete, remaining, start, tweenValue)
      */
    }, initialParams),

    elements: els,
    animating: false,
    que: [],

    easingProgress (easing, progress) {
      if (easing === 'swing') {
        return 0.5 - (Math.cos(progress * Math.PI) / 2)
      }
      if (typeof easing === 'function') {
        return easing(progress)
      }
      return progress
    },
    stop () {
      if (a.frameId) {
        cancelAnimationFrame(a.frameId)
      }
      a.animating = false
      a.elements.each((index, el) => {
        const element = el
        delete element.dom7AnimateInstance
      })
      a.que = []
    },
    done (complete) {
      a.animating = false
      a.elements.each((index, el) => {
        const element = el
        delete element.dom7AnimateInstance
      })
      if (complete) complete(els)
      if (a.que.length > 0) {
        const que = a.que.shift()
        a.animate(que[0], que[1])
      }
    },
    animate (props, params) {
      if (a.animating) {
        a.que.push([props, params])
        return a
      }
      const elements = []

      // Define & Cache Initials & Units
      a.elements.each((index, el) => {
        let initialFullValue
        let initialValue
        let unit
        let finalValue
        let finalFullValue

        if (!el.dom7AnimateInstance) a.elements[index].dom7AnimateInstance = a

        elements[index] = {
          container: el
        }
        Object.keys(props).forEach((prop) => {
          initialFullValue = window.getComputedStyle(el, null).getPropertyValue(prop).replace(',', '.')
          initialValue = parseFloat(initialFullValue)
          unit = initialFullValue.replace(initialValue, '')
          finalValue = parseFloat(props[prop])
          finalFullValue = props[prop] + unit
          elements[index][prop] = {
            initialFullValue,
            initialValue,
            unit,
            finalValue,
            finalFullValue,
            currentValue: initialValue
          }
        })
      })

      let startTime = null
      let time
      let elementsDone = 0
      let propsDone = 0
      let done
      let began = false

      a.animating = true

      function render () {
        time = new Date().getTime()
        let progress
        let easeProgress
        // let el;
        if (!began) {
          began = true
          if (params.begin) params.begin(els)
        }
        if (startTime === null) {
          startTime = time
        }
        if (params.progress) {
          // eslint-disable-next-line
          params.progress(els, Math.max(Math.min((time - startTime) / params.duration, 1), 0), ((startTime + params.duration) - time < 0 ? 0 : (startTime + params.duration) - time), startTime);
        }

        elements.forEach((element) => {
          const el = element
          if (done || el.done) return
          Object.keys(props).forEach((prop) => {
            if (done || el.done) return
            progress = Math.max(Math.min((time - startTime) / params.duration, 1), 0)
            easeProgress = a.easingProgress(params.easing, progress)
            const {
              initialValue,
              finalValue,
              unit
            } = el[prop]
            el[prop].currentValue = initialValue + (easeProgress * (finalValue - initialValue))
            const currentValue = el[prop].currentValue

            if (
              (finalValue > initialValue && currentValue >= finalValue) ||
              (finalValue < initialValue && currentValue <= finalValue)) {
              el.container.style[prop] = finalValue + unit
              propsDone += 1
              if (propsDone === Object.keys(props).length) {
                el.done = true
                elementsDone += 1
              }
              if (elementsDone === elements.length) {
                done = true
              }
            }
            if (done) {
              a.done(params.complete)
              return
            }
            el.container.style[prop] = currentValue + unit
          })
        })
        if (done) return
        // Then call
        a.frameId = requestAnimationFrame(render)
      }
      a.frameId = requestAnimationFrame(render)
      return a
    }
  }

  if (a.elements.length === 0) {
    return els
  }

  let animateInstance
  for (let i = 0; i < a.elements.length; i += 1) {
    if (a.elements[i].dom7AnimateInstance) {
      animateInstance = a.elements[i].dom7AnimateInstance
    } else a.elements[i].dom7AnimateInstance = a
  }
  if (!animateInstance) {
    animateInstance = a
  }

  if (initialProps === 'stop') {
    animateInstance.stop()
  } else {
    animateInstance.animate(a.props, a.params)
  }

  return els
}

function stop () {
  const els = this
  for (let i = 0; i < els.length; i += 1) {
    if (els[i].dom7AnimateInstance) {
      els[i].dom7AnimateInstance.stop()
    }
  }
}

const noTrigger = ('resize scroll').split(' ')

function eventShortcut (name, ...args) {
  if (typeof args[0] === 'undefined') {
    for (let i = 0; i < this.length; i += 1) {
      if (noTrigger.indexOf(name) < 0) {
        if (name in this[i]) this[i][name]()
        else {
          $(this[i]).trigger(name)
        }
      }
    }
    return this
  }
  return this.on(name, ...args)
}

function click (...args) {
  return eventShortcut.bind(this)('click', ...args)
}

function blur (...args) {
  return eventShortcut.bind(this)('blur', ...args)
}

function focus (...args) {
  return eventShortcut.bind(this)('focus', ...args)
}

function focusin (...args) {
  return eventShortcut.bind(this)('focusin', ...args)
}

function focusout (...args) {
  return eventShortcut.bind(this)('focusout', ...args)
}

function keyup (...args) {
  return eventShortcut.bind(this)('keyup', ...args)
}

function keydown (...args) {
  return eventShortcut.bind(this)('keydown', ...args)
}

function keypress (...args) {
  return eventShortcut.bind(this)('keypress', ...args)
}

function submit (...args) {
  return eventShortcut.bind(this)('submit', ...args)
}

function change (...args) {
  return eventShortcut.bind(this)('change', ...args)
}

function mousedown (...args) {
  return eventShortcut.bind(this)('mousedown', ...args)
}

function mousemove (...args) {
  return eventShortcut.bind(this)('mousemove', ...args)
}

function mouseup (...args) {
  return eventShortcut.bind(this)('mouseup', ...args)
}

function mouseenter (...args) {
  return eventShortcut.bind(this)('mouseenter', ...args)
}

function mouseleave (...args) {
  return eventShortcut.bind(this)('mouseleave', ...args)
}

function mouseout (...args) {
  return eventShortcut.bind(this)('mouseout', ...args)
}

function mouseover (...args) {
  return eventShortcut.bind(this)('mouseover', ...args)
}

function touchstart (...args) {
  return eventShortcut.bind(this)('touchstart', ...args)
}

function touchend (...args) {
  return eventShortcut.bind(this)('touchend', ...args)
}

function touchmove (...args) {
  return eventShortcut.bind(this)('touchmove', ...args)
}

function resize (...args) {
  return eventShortcut.bind(this)('resize', ...args)
}

function scroll (...args) {
  return eventShortcut.bind(this)('scroll', ...args)
}

export {
  $,
  addClass,
  removeClass,
  hasClass,
  toggleClass,
  attr,
  removeAttr,
  prop,
  data,
  removeData,
  dataset,
  val,
  transform,
  transition,
  on,
  off,
  once,
  trigger,
  transitionEnd,
  animationEnd,
  width,
  outerWidth,
  height,
  outerHeight,
  offset,
  hide,
  show,
  styles,
  css,
  toArray,
  each,
  forEach,
  filter,
  map,
  html,
  text,
  is,
  indexOf,
  index,
  eq,
  append,
  appendTo,
  prepend,
  prependTo,
  insertBefore,
  insertAfter,
  next,
  nextAll,
  prev,
  prevAll,
  siblings,
  parent,
  parents,
  closest,
  find,
  children,
  remove,
  detach,
  add,
  empty,
  scrollTo,
  scrollTop,
  scrollLeft,
  animate,
  stop,
  click,
  blur,
  focus,
  focusin,
  focusout,
  keyup,
  keydown,
  keypress,
  submit,
  change,
  mousedown,
  mousemove,
  mouseup,
  mouseenter,
  mouseleave,
  mouseout,
  mouseover,
  touchstart,
  touchend,
  touchmove,
  resize,
  scroll
}
